/* Copyright 2023 Manuel Jinger
 * Copyright 2023 Dual Tachyon
 * https://github.com/DualTachyon
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *     Unless required by applicable law or agreed to in writing, software
 *     distributed under the License is distributed on an "AS IS" BASIS,
 *     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *     See the License for the specific language governing permissions and
 *     limitations under the License.
 */

#include "bsp/dp32g030/gpio.h"
#include "driver/gpio.h"
#include "driver/keyboard.h"
#include "driver/systick.h"
#include "driver/i2c.h"
#include "misc.h"

KEY_Code_t gKeyReading0     = KEY_INVALID;
KEY_Code_t gKeyReading1     = KEY_INVALID;
uint16_t   gDebounceCounter = 0;
bool       gWasFKeyPressed  = false;

static const struct {

    // Using a 16 bit pre-calculated shift and invert is cheaper
    // than using 8 bit and doing shift and invert in code.
    uint16_t set_to_zero_mask;

    // We are very fortunate.
    // The key and pin defines fit together in a single u8, making this very efficient
    struct {
        KEY_Code_t key : 5;
        uint8_t    pin : 3; // Pin 6 is highest
    } pins[4];

} keyboard[] = {

    {   // Zero row
        // Set to zero to handle special case of nothing pulled down
        .set_to_zero_mask = 0xffff,
        .pins = {
            { .key = KEY_SIDE1,   .pin = GPIOA_PIN_KEYBOARD_0},
            { .key = KEY_SIDE2,   .pin = GPIOA_PIN_KEYBOARD_1},

            // Duplicate to fill the array with valid values
            { .key = KEY_INVALID, .pin = GPIOA_PIN_KEYBOARD_1},
            { .key = KEY_INVALID, .pin = GPIOA_PIN_KEYBOARD_1}
        }
    },
    {   // First row
        .set_to_zero_mask = ~(1u << GPIOA_PIN_KEYBOARD_4) & 0xffff,
        .pins = {
            { .key = KEY_MENU,  .pin = GPIOA_PIN_KEYBOARD_0},
            { .key = KEY_1,     .pin = GPIOA_PIN_KEYBOARD_1},
            { .key = KEY_4,     .pin = GPIOA_PIN_KEYBOARD_2},
            { .key = KEY_7,     .pin = GPIOA_PIN_KEYBOARD_3}
        }
    },
    {   // Second row
        .set_to_zero_mask = ~(1u << GPIOA_PIN_KEYBOARD_5) & 0xffff,
        .pins = {
            { .key = KEY_UP,    .pin = GPIOA_PIN_KEYBOARD_0},
            { .key = KEY_2 ,    .pin = GPIOA_PIN_KEYBOARD_1},
            { .key = KEY_5 ,    .pin = GPIOA_PIN_KEYBOARD_2},
            { .key = KEY_8 ,    .pin = GPIOA_PIN_KEYBOARD_3}
        }
    },
    {   // Third row
        .set_to_zero_mask = ~(1u << GPIOA_PIN_KEYBOARD_6) & 0xffff,
        .pins = {
            { .key = KEY_DOWN,  .pin = GPIOA_PIN_KEYBOARD_0},
            { .key = KEY_3   ,  .pin = GPIOA_PIN_KEYBOARD_1},
            { .key = KEY_6   ,  .pin = GPIOA_PIN_KEYBOARD_2},
            { .key = KEY_9   ,  .pin = GPIOA_PIN_KEYBOARD_3}
        }
    },
    {   // Fourth row
        .set_to_zero_mask = ~(1u << GPIOA_PIN_KEYBOARD_7) & 0xffff,
        .pins = {
            { .key = KEY_EXIT,  .pin = GPIOA_PIN_KEYBOARD_0},
            { .key = KEY_STAR,  .pin = GPIOA_PIN_KEYBOARD_1},
            { .key = KEY_0   ,  .pin = GPIOA_PIN_KEYBOARD_2},
            { .key = KEY_F   ,  .pin = GPIOA_PIN_KEYBOARD_3}
        }
    }
};

KEY_Code_t KEYBOARD_Poll(void)
{
    KEY_Code_t Key = KEY_INVALID;

//  if (!GPIO_CheckBit(&GPIOC->DATA, GPIOC_PIN_PTT))
//      return KEY_PTT;

    // *****************

    for (unsigned int j = 0; j < ARRAY_SIZE(keyboard); j++)
    {
        uint16_t reg;
        unsigned int i;
        unsigned int k;

        // Set all high
        GPIOA->DATA |=  1u << GPIOA_PIN_KEYBOARD_4 |
                        1u << GPIOA_PIN_KEYBOARD_5 |
                        1u << GPIOA_PIN_KEYBOARD_6 |
                        1u << GPIOA_PIN_KEYBOARD_7;

        // Clear the pin we are selecting
        GPIOA->DATA &= keyboard[j].set_to_zero_mask;

        // Read all 4 GPIO pins at once .. with de-noise, max of 8 sample loops
        for (i = 0, k = 0, reg = 0; i < 3 && k < 8; i++, k++) {
            SYSTICK_DelayUs(1);
            uint16_t reg2 = GPIOA->DATA;
            i *= reg == reg2;
            reg = reg2;
        }

        if (i < 3)
            break;  // noise is too bad

        for (unsigned int i = 0; i < ARRAY_SIZE(keyboard[j].pins); i++)
        {
            const uint16_t mask = 1u << keyboard[j].pins[i].pin;
            if (!(reg & mask))
            {
                Key = keyboard[j].pins[i].key;
                break;
            }
        }

        if (Key != KEY_INVALID)
            break;
    }

    // Create I2C stop condition since we might have toggled I2C pins
    // This leaves GPIOA_PIN_KEYBOARD_4 and GPIOA_PIN_KEYBOARD_5 high
    I2C_Stop();

    // Reset VOICE pins
    GPIO_ClearBit(&GPIOA->DATA, GPIOA_PIN_KEYBOARD_6);
    GPIO_SetBit(  &GPIOA->DATA, GPIOA_PIN_KEYBOARD_7);

    return Key;
}
